package com.fengxu.script

import android.util.Log
import com.fengxu.gamescript.jsengine.annotation.JsCallJava
import com.fengxu.gamescript.jsengine.api.IJsObject
import com.fengxu.gamescript.jsengine.core.AndroidContextFactory
import org.mozilla.javascript.Context
import org.mozilla.javascript.ContextFactory
import org.mozilla.javascript.Function
import org.mozilla.javascript.ImporterTopLevel
import org.mozilla.javascript.ScriptableObject
import java.lang.Exception
import java.util.*
import kotlin.properties.Delegates

class JsEngine {

    // rhino引擎
    var rhino: Context by Delegates.notNull()
        private set

    // 获取标准作用域
    var scope: ScriptableObject by Delegates.notNull()
        private set

    // 脚本发生的错误事件
    private val errorEvents: MutableList<(Throwable)->Unit> = mutableListOf()

    init {
        if(ContextFactory.getGlobal() !is AndroidContextFactory){
            ContextFactory.getGlobalSetter().contextFactoryGlobal = AndroidContextFactory()
        }
    }

    companion object{
        // js全局对象的名称
        val GlobalName = "window"
    }

    /**
     * 注入全局属性
     * @param name 注入名称
     * @param value 注入值
     * @param isConst 是否可更改
     */
    fun injectProp(name: String, value:Any, isConst:Boolean =  false){
        if(isConst){
            ScriptableObject.putConstProperty(scope,name,Context.javaToJS(value, scope))
        }else{
            ScriptableObject.putProperty(scope,name,Context.javaToJS(value, scope))
        }
    }

    /**
     * 注入全局对象
     * @param name 注入名称
     * @param value 注入值
     * @param isConst 是否可更改
     */
    fun injectProp(jsObj: IJsObject, isConst:Boolean =  false){
        if(isConst){
            ScriptableObject.putConstProperty(scope,jsObj.nameInJs,Context.javaToJS(jsObj, scope))
        }else{
            ScriptableObject.putProperty(scope,jsObj.nameInJs,Context.javaToJS(jsObj, scope))
        }
        mountPropToGlobal(jsObj)
    }

    /**
     * 挂载IJsObject中的带有注解JsCallJava的方法/属性到全局作用域
     * @param jsObj 待挂载对象
     */
    private fun mountPropToGlobal(jsObj:IJsObject){
        // 扫描公开字段
        for (field in jsObj.javaClass.fields) {
            if(!field.isAnnotationPresent(JsCallJava::class.java)) continue
            val annotation = field.getAnnotation(JsCallJava::class.java)
            val value = field.get(jsObj)
            val name = if(annotation.value.isEmpty()){field.name}else{annotation.value}
            injectProp(name,value,annotation.isConst)
        }
        // 扫描公开方法
        for (method in jsObj.javaClass.methods) {
            if(!method.isAnnotationPresent(JsCallJava::class.java)) continue
            var annotation = method.getAnnotation(JsCallJava::class.java)
            val methodName = if(annotation.value.isEmpty()){method.name}else{annotation.value}
            exec("""
                ${GlobalName}["${methodName}"] = function(){
                    return ${jsObj.nameInJs}["${methodName}"].apply(${jsObj.nameInJs}, arguments)
                }
            """.trimIndent())
        }

    }

    /**
     * 调用js方法
     */
    fun callJsFunc(funcName: String, vararg args:Any):Any?{
        val function = scope.get(funcName, scope) as Function
        return function.call(rhino,scope,scope, args)
    }

    /**
     * 执行js代码
     */
    fun exec(code: String){
        rhino.evaluateString(scope, code,JsEngine::class.java.simpleName,1,null)
    }

    /**
     * 重置脚本环境
     */
    fun reset(){
        rhino = Context.enter()
        rhino.optimizationLevel = -1
        rhino.locale = Locale.getDefault()
        rhino.languageVersion = Context.VERSION_ES6
        val importerTopLevel = ImporterTopLevel()
        importerTopLevel.initStandardObjects(rhino,false)
        scope = importerTopLevel
        exec("const $GlobalName = this; ")
    }

    /**
     * 将class数组转为[java.lang.String,...]这种类型
     */
    private fun classArrToStr(classArr:Array<Class<*>>): String{
        var sb = StringBuilder("[")
        for (clazz in classArr) {
            sb.append(clazz.name + ",")
        }
        if(sb.last() == ','){
            sb.deleteCharAt(sb.length - 1)
        }
        sb.append(']')
        return sb.toString()
    }

    /**
     * 注册一个脚本错误事件
     */
    fun registerError(event: (Throwable)->Unit){
        errorEvents.add(event)
    }

    /**
     * 发布脚本的错误事件
     */
    fun dispatchError(error: Throwable){
        errorEvents.forEach { it.invoke(error) }
    }

    /**
     * 退出js环境
     */
    fun exit(){
        try {
            println("=============exitScript====onThread:${Thread.currentThread().name}====")
            Context.exit()
        }catch (e:Exception){/*ignore*/}
    }


}